package com.hunter.jerrymouse.request.netty;

import com.alibaba.fastjson.JSON;
import com.hunter.jerrymouse.annotation.request.JsonBody;
import com.hunter.jerrymouse.request.Request;
import com.hunter.jerrymouse.request.RequestHandle;
import com.hunter.jerrymouse.request.RequestParameter;
import com.hunter.jerrymouse.response.Response;
import com.hunter.jerrymouse.response.netty.NettyResponse;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.handler.codec.http.multipart.Attribute;
import io.netty.handler.codec.http.multipart.FileUpload;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
import io.netty.handler.codec.http.multipart.InterfaceHttpData;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.*;

import static java.nio.charset.StandardCharsets.UTF_8;

/**
 * Created by hunter
 * <p>
 * 请求参数解析器, 即将请求的参数信息解析为对应处理方法的参数形式数组返回
 */
public class RequestParamParser {

    /**
     * 基础类型默认值
     */
    private static byte DEFAULT_BYTE_VAL;
    private static short DEFAULT_SHORT_VAL;
    private static int DEFAULT_INT_VAL;
    private static long DEFAULT_LONG_VAL;
    private static float DEFAULT_FLOAT_VAL;
    private static double DEFAULT_DOUBLE_VAL;
    private static char DEFAULT_CHAR_VAL;
    private static boolean DEFAULT_BOOLEAN_VAL;


    /**
     * 基础类型的装箱类型集合
     */
    private static final List<Class<?>> PRIMITIVE_BOX_CLASSES = new ArrayList<>(8);

    static {
        PRIMITIVE_BOX_CLASSES.add(Byte.class);
        PRIMITIVE_BOX_CLASSES.add(Short.class);
        PRIMITIVE_BOX_CLASSES.add(Integer.class);
        PRIMITIVE_BOX_CLASSES.add(Long.class);
        PRIMITIVE_BOX_CLASSES.add(Float.class);
        PRIMITIVE_BOX_CLASSES.add(Double.class);
        PRIMITIVE_BOX_CLASSES.add(Boolean.class);
        PRIMITIVE_BOX_CLASSES.add(Character.class);
    }

    /**
     * 默认的字符格式
     */
    private static final Charset DEFAULT_CHARSET = UTF_8;

    /**
     * 根据请求解析对应处理方法需要的参数列表
     *
     * @RETURN
     */
    public static Object[] parseRequestParameters(ChannelHandlerContext ctx, RequestHandle handle, NettyRequest request) throws IOException {
        // 获取方法的参数列表
        RequestParameter[] parameters = handle.getRequestParameters();
        if (parameters.length == 0) {
            return null;
        }
        // 请求的http method
        HttpMethod httpMethod = request.getMethod();

        if (HttpMethod.GET.equals(httpMethod)) {
            return parseGetRequestParameters(ctx, request, parameters);

        } else if (HttpMethod.POST.equals(httpMethod)) {
            return parsePostRequestParameters(ctx, request, parameters);

        } else if (HttpMethod.DELETE.equals(httpMethod)) { // 可同post方法处理
            return parsePostRequestParameters(ctx, request, parameters);

        } else if (HttpMethod.PUT.equals(httpMethod)) { // 可同post方法处理
            return parsePostRequestParameters(ctx, request, parameters);
        } else {
            // TODO, Other method
            // TODO 暂用runtime异常抛出
            throw new RuntimeException("Unsupported http method.");
        }
    }

    /**
     * 解析post请求参数
     *
     * @param ctx
     * @param request
     * @param parameters
     * @return
     */
    private static Object[] parsePostRequestParameters(ChannelHandlerContext ctx, NettyRequest request, RequestParameter[] parameters) throws IOException {
        String contentType = getRequestContentType(request);

        Object[] resultObjects;
        if (HttpHeaderValues.APPLICATION_JSON.toString().equals(contentType)) {
            // json单独拎出来
            int length = parameters.length;
            resultObjects = new Object[length];
            for (int i = 0; i < length; i++) {
                RequestParameter requestParameter = parameters[i];
                try {
                    if (Response.class.equals(requestParameter.getClassType())) { // 如果是填充ctx属性
                        resultObjects[i] = new NettyResponse(ctx);
                    } else if (Request.class.equals(requestParameter.getClassType())) {// 如果是填充request属性,
                        resultObjects[i] = request;
                    } else if (requestParameter.getAnnotations().length > 0 // 如果注解了@JsonBody
                            && Arrays.stream(requestParameter.getAnnotations()).anyMatch(annotation ->
                            annotation.annotationType().equals(JsonBody.class))) {
                        resultObjects[i] = JSON.parseObject(request.getRequestData().toString(DEFAULT_CHARSET), requestParameter.getClassType());
                    }
                } catch (Exception e) {
                    throw new RuntimeException(requestParameter.getName() + " param parse error.");
                }
            }

        } else if (HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.toString().equals(contentType)) {
            // 类似GET请求的处理
            return parseGetRequestParameters(ctx, request, parameters);

        } else if (HttpHeaderValues.MULTIPART_FORM_DATA.toString().equals(contentType)) {
            // 使用HttpPostRequestDecoder解码, 转为map再继续映射返回
            HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(request);
            List<InterfaceHttpData> dataList = decoder.getBodyHttpDatas();
            Map<String, Object> parameterValMap = new HashMap<>(dataList.size());
            for (InterfaceHttpData data : dataList) {
                if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.FileUpload) {
                    parameterValMap.put(data.getName(), new NettyFileRequest((FileUpload) data));
                } else if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.Attribute) {
                    Attribute attribute = (Attribute) data;
                    parameterValMap.put(attribute.getName(), attribute.getValue());
                }
            }

            resultObjects = getInvokeArguments(ctx, request, parameters, parameterValMap);
        } else {
            // TODO, Other type
            // TODO 暂用runtime异常抛出
            throw new RuntimeException("Unsupported request content type.");
        }

        // help gc
        request.getRequestData().release();

        return resultObjects;
    }

    /**
     * 解析get请求的对应的请求参数
     *
     * @param ctx
     * @param request
     * @param parameters
     * @return
     */
    public static Object[] parseGetRequestParameters(ChannelHandlerContext ctx, NettyRequest request, RequestParameter[] parameters) {
        // 解析参数, 优先从content里拿, 拿不到拿url上的, 最后装箱反射参数列表
        QueryStringDecoder queryDecoder = new QueryStringDecoder(request.getRequestData().toString(DEFAULT_CHARSET), false);
        Map<String, List<String>> parameterValListMap = queryDecoder.parameters();
        if (CollectionUtils.isEmpty(parameterValListMap)) {
            parameterValListMap = new QueryStringDecoder(request.getUri(), true).parameters();
        }
        Map<String, Object> parameterValMap = new HashMap<>(parameterValListMap.size());
        for (Map.Entry<String, List<String>> entry : parameterValListMap.entrySet()) {
            parameterValMap.put(entry.getKey(), CollectionUtils.isEmpty(entry.getValue()) ? null : entry.getValue().get(0));
        }
        Object[] objects = getInvokeArguments(ctx, request, parameters, parameterValMap);

        // help gc
        request.getRequestData().release();

        return objects;
    }

    /**
     * 获取请求内容格式
     *
     * @param request
     * @return
     */
    private static String getRequestContentType(NettyRequest request) {
        String contentType = request.headers().get(HttpHeaderNames.CONTENT_TYPE);
        if (contentType == null) {
            return null;
        }

        return contentType.split(";")[0];
    }

    /**
     * 根据方法参数信息列表组装对应的 invoke对象数组
     *
     * @param ctx
     * @param request
     * @param parameters
     * @param parameterValMap
     * @return
     */
    private static Object[] getInvokeArguments(ChannelHandlerContext ctx, NettyRequest request, RequestParameter[] parameters, Map<String, Object> parameterValMap) {
        int length = parameters.length;
        Object[] objects = new Object[length];
        for (int i = 0; i < length; i++) {

            RequestParameter parameter = parameters[i];

            // 如果是填充ctx和request属性, 后续过程就免去
            if (Response.class.equals(parameter.getClassType())) {
                objects[i] = new NettyResponse(ctx);
                continue;
            } else if (Request.class.equals(parameter.getClassType())) {
                request.setParamMap(parameterValMap);
                objects[i] = request;
                continue;
            }

            Object value = parameterValMap.get(parameter.getName());
            try {
                if (value == null) {
                    // 基础类型的处理, 不然会报异常
                    if (parameter.getClassType().isPrimitive()) {
                        objects[i] = getPrimitiveValue(parameter.getClassType());
                    } else {
                        objects[i] = null;
                    }
                } else {
                    // 如果是基础类型或者包装类, 用JSON工具将String转为基础类型, 其他的直接赋值(JSON类型的已单独拉出来做处理)
                    if (parameter.getClassType().isPrimitive() || isPrimitiveBoxClass(parameter.getClassType())) {
                        objects[i] = JSON.parseObject(value.toString(), parameter.getClassType());
                    } else {
                        objects[i] = value;
                    }
                }
            } catch (Exception e) {
                // TODO 暂时先用runtime异常处理
                throw new RuntimeException(parameter.getName() + " param parse error.");
            }
        }

        return objects;
    }

    /**
     * 获取基础类型默认值
     *
     * @param type
     * @return
     */
    private static Object getPrimitiveValue(Class<?> type) {
        if (byte.class.equals(type)) {
            return DEFAULT_BYTE_VAL;
        } else if (short.class.equals(type)) {
            return DEFAULT_SHORT_VAL;
        } else if (int.class.equals(type)) {
            return DEFAULT_INT_VAL;
        } else if (long.class.equals(type)) {
            return DEFAULT_LONG_VAL;
        } else if (float.class.equals(type)) {
            return DEFAULT_FLOAT_VAL;
        } else if (double.class.equals(type)) {
            return DEFAULT_DOUBLE_VAL;
        } else if (char.class.equals(type)) {
            return DEFAULT_CHAR_VAL;
        } else {
            return DEFAULT_BOOLEAN_VAL;
        }
    }

    /**
     * 是否是基础类型的包装类
     *
     * @param type
     * @return
     */
    private static boolean isPrimitiveBoxClass(Class<?> type) {
        return PRIMITIVE_BOX_CLASSES.contains(type);
    }
}
